<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

/**
 * 小程序支付 对应微信支付 V3
 * @author admin
 *
 */

class SmallPayV3
{
    public $appid = '';       //小程序id
    protected $mch_id = '';    //商户id
    protected $key = '';          //商户key
    private   $appSecret = ''; //小程序后台appSecret
    private   $apiclient_cert = ''; //商户证书
    private   $apiclient_key = ''; //商户密匙

    public function __construct()
    {

        //获取支付配置文件参数
        $config = config('smallpay');
        $this->appid      = $config['appid'];
        $this->mch_id     = $config['mch_id'];
        $this->key        = $config['key'];
        $this->appSecret  = $config['appSecret'];

        $this->apiclient_cert = public_path('uploads') . '/cert/apiclient_cert.pem'; //商户证书
        $this->apiclient_key = public_path('uploads') . '/cert/apiclient_key.pem'; //商户密匙
    }

    /***
     *商家转账到零钱

        //转账成功正常示例
         {
            "out_batch_no": "plfk2020042013",
            "batch_id": "1030000071100999991182020050700019480001",
            "create_time": "2015-05-20T13:29:35.120+08:00"
            }

            //实际是这样
             {
            "batch_status": "ACCEPTED",
            "out_batch_no": "plfk2020042013",
            "batch_id": "1030000071100999991182020050700019480001",
            "create_time": "2015-05-20T13:29:35.120+08:00"
            }

            转账失败示例
            {"code":"NOT_ENOUGH","message":"资金不足"}
     */
    public function wechatTransfer($order_id, $price, $open_id, $batch_name, $packet_msg, $username = null)
    {
        $price = sprintf("%.0f", $price); //强制转换为整形，解决 浮点数  509.99999999999994 使用 intval() 或  （int）强制转换为 509 的bug
        $price = intval($price);
        
        $xml_data = $this->buildParams($order_id, $price, $open_id, $batch_name, $packet_msg, $username); //构建参数
        $url = 'https://api.mch.weixin.qq.com/v3/transfer/batches'; //商家转账到零钱

        $xml_datas = json_encode($xml_data);
        $token = $this->getToken($url, 'POST', time(), $xml_datas); //获取header Token认证
        $res_xml = $this->https_request($url, $xml_datas, $token); //请求
        $resArr = json_decode($res_xml, true); //请求返回

        if (!empty($resArr['code']) && !empty($resArr['message'])) {
            return $resArr['code'] . '~' . $resArr['message']; //转账失败
        }

        if (!empty($resArr['out_batch_no']) && !empty($resArr['batch_id']) && !empty($resArr['create_time'])) {
            //转账提交成功，走转账提交成功的逻辑
            return true;
        }
        return '未知结果，请到微信支付商户后台查看';
    }

    /**
     *请求接口参数获取
     */
    protected function getSelfParams($order_id, $price, $open_id, $batch_name, $packet_msg, $username = null)
    {
        $self_params = [
            "appid" => $this->appid, //申请商户号的appid或商户号绑定的appid
            "out_batch_no" => $order_id . '1', //商户系统内部的商家批次单号，要求此参数只能由数字、大小写字母组成，在商户系统内部唯一  多加一个数字 1，和订单号区分开 ，无任何意义
            "batch_name" => $batch_name, //该笔批量转账的名称
            "batch_remark" => $packet_msg, //转账说明，UTF8编码，最多允许32个字符
            'total_amount' => $price, //转账金额单位为“分”。转账总金额必须与批次内所有明细转账金额之和保持一致，否则无法发起转账操作
            'total_num' => 1, //一笔一笔转
            'transfer_detail_list' => [
                [
                    'out_detail_no' => $order_id,
                    'transfer_amount' => $price,
                    'transfer_remark' => $packet_msg,
                    'openid' => $open_id,
                    //'user_name' => '',//无值就不要
                ]
            ] //发起批量转账的明细列表
        ];

        if ($price >= 200000) {
            if (empty($username)) {
                throw new \Exception('转账金额 >= 2000元，收款用户真实姓名必填');
            }
            $data['transfer_detail_list'][0]['user_name'] = $this->getEncrypt($username);
        }
        return $self_params;
    }
    /**
     * @notes 敏感信息加解密
     */
    public function getEncrypt($str)
    {
        //$str是待加密字符串
        $public_key = file_get_contents($this->apiclient_cert);
        $encrypted = '';
        if (openssl_public_encrypt($str, $encrypted, $public_key, OPENSSL_PKCS1_OAEP_PADDING)) {
            //base64编码
            $sign = base64_encode($encrypted);
        } else {
            throw new \Exception('encrypt failed');
        }
        return $sign;
    }

    /***
     **构建参数
     */
    protected function buildParams($order_id, $price, $open_id, $batch_name, $packet_msg, $username = null)
    {
        $params = [
            'appid'      => $this->appid, //appid
            'mch_id'     =>  $this->mch_id, //商户号
        ];
        if (!empty($order_id)) {
            $self_params = $this->getSelfParams($order_id, $price, $open_id, $batch_name, $packet_msg, $username); //获取参数
            if (is_array($self_params) && !empty($self_params)) {
                $params = array_merge($params, $self_params); //合并参数
            }
        }
        $params = $this->paraFilter($params); //过滤参数
        $params = $this->arraySort($params); //参数排序
        return $params;
    }

    /**
     *获取认证
     */
    public function getToken($url, $http_method, $timestamp, $body = '')
    {
        // $url = 'https://api.mch.weixin.qq.com/v3/certificates';
        $url_parts   = parse_url($url); //获取请求的绝对URL
        $onoce_str = $this->getRandChar(32);
        $stream_opts = [
            "ssl" => [
                "verify_peer" => false,
                "verify_peer_name" => false,
            ]
        ];
        $apiclient_cert_path = $this->apiclient_cert; //商户证书
        $apiclient_key_path  = $this->apiclient_key; //商户密匙
        $apiclient_cert_arr = openssl_x509_parse(file_get_contents($apiclient_cert_path, false, stream_context_create($stream_opts)));
        $serial_no          = $apiclient_cert_arr['serialNumberHex']; //证书序列号(忽略

        //$serial_no          = '636E9DEF6F106FA6EB4D5344590CD165860B3B96'; //证书序列号(忽略)
        $mch_private_key    = file_get_contents($apiclient_key_path, false, stream_context_create($stream_opts)); //密钥
        $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
        $message = $http_method . "\n" .
            $canonical_url . "\n" .
            $timestamp . "\n" .
            $onoce_str . "\n" .
            $body . "\n";
        openssl_sign($message, $raw_sign, $mch_private_key, 'sha256WithRSAEncryption');
        $sign = base64_encode($raw_sign); //签名
        $schema = 'WECHATPAY2-SHA256-RSA2048 '; //有个空格
        $token = sprintf(
            'mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
            $this->mch_id,
            $onoce_str,
            $timestamp,
            $serial_no,
            $sign
        ); //微信返回token
        return $schema . ' ' . $token;
    }

    public function paraFilter($para)
    {
        $paraFilter = [];
        foreach ($para as $key => $val) {
            if ($val === '' || $val === null) {
                continue;
            }
            if (!is_array($para[$key])) {
                if (!is_numeric($para[$key])) {
                    $para[$key] = is_bool($para[$key]) ? $para[$key] : trim($para[$key]);
                }
            }

            $paraFilter[$key] = $para[$key];
        }

        return $paraFilter;
    }

    public function arraySort(array $param)
    {
        ksort($param);
        reset($param);

        return $param;
    }

    //获取指定长度的随机字符串
    function getRandChar($length)
    {
        $str = null;
        $strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
        $max = strlen($strPol) - 1;

        for ($i = 0; $i < $length; $i++) {
            $str .= $strPol[rand(0, $max)]; //rand($min,$max)生成介于min和max两个数之间的一个随机整数
        }

        return $str;
    }

    /**
     * 商家明细单号查询(获取转账结果)
     */
    public function getExecuteResult($order_id)
    {
        if (empty($order_id)) {
            return false;
        }
        //请求URL
        $url = 'https://api.mch.weixin.qq.com/v3/transfer/batches/out-batch-no/' . $order_id . '1' . '/details/out-detail-no/' . $order_id;
        //请求方式
        $http_method = 'GET';
        //请求参数
        $token  = $this->getToken($url, $http_method, time(), null); //获取token
        $result = $this->https_request($url, null, $token); //请求
        $result_arr = json_decode($result, true);
        return $result_arr;
    }
    /**
     *请求接口
     */
    function https_request($url, $data = null, $token = '')
    {
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, (string)$url);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
        if (!empty($data)) {
            curl_setopt($curl, CURLOPT_POST, 1);
            curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
        }
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        //添加请求头
        $headers = [
            'Authorization:' . $token,
            'Accept: application/json',
            'Content-Type: application/json; charset=utf-8',
            'User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36',
        ];
        if (!empty($headers)) {
            curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
        }
        $output = curl_exec($curl);
        curl_close($curl);
        return $output;
    }
}
